package com.octopus.optiworks.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * GenericHibernateDao extends HibernateDaoSupport, encapsulate functionalities of HigernateTemplate, facilitate Hibernate Doa coding
 */
@SuppressWarnings("unchecked")
public class GenericHibernateDao<T extends Serializable, PK extends Serializable>
		extends HibernateDaoSupport implements GenericDao<T, PK> {
	
	@Autowired  
	public void setSessionFactory0(SessionFactory sessionFactory){  
	  super.setSessionFactory(sessionFactory);  
	}  
	
	private Class<T> entityClass;

	public GenericHibernateDao() {
		this.entityClass = null;
		Class c = getClass();
		Type t = c.getGenericSuperclass();
		if (t instanceof ParameterizedType) {
			Type[] p = ((ParameterizedType) t).getActualTypeArguments();
			this.entityClass = (Class<T>) p[0];
		}
	}

	// -------------------- Basic query, insert, update, update --------------------

	// Get entity by PK, if no entity , return null.
	public T get(PK id) {
		return (T) getHibernateTemplate().get(entityClass, id);
	}

	// Get entity and add lock by PK, if no entity, return null.
	public T getWithLock(PK id, LockMode lock) {
		T t = (T) getHibernateTemplate().get(entityClass, id, lock);
		if (t != null) {
			this.flush(); // Flush immediately, otherwise lock can not take effect.
		}
		return t;
	}
	
	// Load entity by PK, if no entity, throw exception.
	public T load(PK id) {
		return (T) getHibernateTemplate().load(entityClass, id);
	}

	// Load entity and add lock by PK, if no entity, throw exception.
	public T loadWithLock(PK id, LockMode lock) {
		T t = (T) getHibernateTemplate().load(entityClass, id, lock);
		if (t != null) {
			this.flush(); // Flush immediately, otherwise lock can not take effect.
		}
		return t;
	}

	// Load all entities.
	public List<T> loadAll() {
		return (List<T>) getHibernateTemplate().loadAll(entityClass);
	}
	
	public List<T> loadAll(final int offset, final int length) {
		return getHibernateTemplate().executeWithNativeSession(new HibernateCallback<List<T>>() {
			public List<T> doInHibernate(Session session) throws HibernateException {
				Criteria criteria = session.createCriteria(entityClass);
				criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
				criteria.setFirstResult(offset);
                criteria.setMaxResults(length);
				return criteria.list();
			}
		});
	}

	// Update entity
	public void update(T entity) {
		getHibernateTemplate().update(entity);
	}

	// Update entity and add lock
	public void updateWithLock(T entity, LockMode lock) {
		getHibernateTemplate().update(entity, lock);
		this.flush(); // Flush immediately, otherwise lock can not take effect.
	}

	// Save entity
	public void save(T entity) {
		getHibernateTemplate().save(entity);
	}

	// Save or update entity
	public void saveOrUpdate(T entity) {
		getHibernateTemplate().saveOrUpdate(entity);
	}

	// Save or update all entities in the cllection
	public void saveOrUpdateAll(Collection<T> entities) {
		getHibernateTemplate().saveOrUpdateAll(entities);
	}

	// Delete entity
	public void delete(T entity) {
		getHibernateTemplate().delete(entity);
	}

	// Delete and add lock of entity
	public void deleteWithLock(T entity, LockMode lock) {
		getHibernateTemplate().delete(entity, lock);
		this.flush(); // Flush immediately, otherwise lock can not take effect.
	}

	// Delete entity by PK
	public void deleteByKey(PK id) {
		this.delete(this.load(id));
	}

	// Delete entity and add lock by PK
	public void deleteByKeyWithLock(PK id, LockMode lock) {
		this.deleteWithLock(this.load(id), lock);
	}

	// Delete all entities in the collection
	public void deleteAll(Collection<T> entities) {
		getHibernateTemplate().deleteAll(entities);
	}

	// -------------------- HSQL ----------------------------------------------

	// Save, update or delete entities by HSQL
	public int bulkUpdate(String queryString) {
		return getHibernateTemplate().bulkUpdate(queryString);
	}

	// Save, update or delete entities by HSQL with parameters
	public int bulkUpdate(String queryString, Object[] values) {
		return getHibernateTemplate().bulkUpdate(queryString, values);
	}

	// Find entities by HSQL
	public List<T> find(String queryString) {
		return getHibernateTemplate().find(queryString);
	}

	// Find entities by HSQL with parameters
	public List<T> find(String queryString, Object[] values) {
		return getHibernateTemplate().find(queryString, values);
	}
	
	public List<T> find(final String queryString, final Object[] values, final int offset, final int length) {
		List<T> list = getHibernateTemplate().executeFind(
				new HibernateCallback<List<T>>() {
					public List<T> doInHibernate(Session session)
							throws HibernateException, SQLException {
						Query query = session.createQuery(queryString);
						query.setFirstResult(offset);
						query.setMaxResults(length);
						if (values != null) {
							for (int i = 0; i < values.length; i++) {
								query.setParameter(i, values[i]);
							}
						}
						List<T> l = query.list();
						return l;
					}
				});
		return list;
	}
	
	public int count(final String queryString, final Object[] values) {
		List list = getHibernateTemplate().executeFind(
				new HibernateCallback<List>() {
					public List doInHibernate(Session session)
							throws HibernateException, SQLException {
						Query query = session.createQuery(queryString);
						if (values != null) {
							for (int i = 0; i < values.length; i++) {
								query.setParameter(i, values[i]);
							}
						}
						List l = query.list();
						return l;
					}
				});
		return ((Long)list.get(0)).intValue();
	}

	// Find entities by HSQL with named parameters
	public List<T> findByNamedParam(String queryString, String[] paramNames,
			Object[] values) {
		return getHibernateTemplate().findByNamedParam(queryString, paramNames,
				values);
	}
	
	// Find entities by HSQL by example
	public List<T> findByExcemple(T t) {
		return getHibernateTemplate().findByExample(t);
	}

	// Find entities by named HSQL
	public List<T> findByNamedQuery(String queryName) {
		return getHibernateTemplate().findByNamedQuery(queryName);
	}

	// Find entities by named HSQL with parameters
	public List<T> findByNamedQuery(String queryName, Object[] values) {
		return getHibernateTemplate().findByNamedQuery(queryName, values);
	}

	// Find entities by named HSQL with named parameters
	public List<T> findByNamedQueryAndNamedParam(String queryName,
			String[] paramNames, Object[] values) {
		return getHibernateTemplate().findByNamedQueryAndNamedParam(queryName,
				paramNames, values);
	}

	// Find entities by HSQL, return iterator
	public Iterator<T> iterate(String queryString) {
		return getHibernateTemplate().iterate(queryString);
	}

	// Find entities by HSQL with parameters, return iterator
	public Iterator<T> iterate(String queryString, Object[] values) {
		return getHibernateTemplate().iterate(queryString, values);
	}

	// Close iterator
	public void closeIterator(Iterator it) {
		getHibernateTemplate().closeIterator(it);
	}

	// -------------------------------- Criteria ------------------------------

	// Create criteria without session
	public DetachedCriteria createDetachedCriteria() {
		return DetachedCriteria.forClass(this.entityClass);
	}

	// Create criteria binded with session
	public Criteria createCriteria() {
		return this.createDetachedCriteria().getExecutableCriteria(
				this.getSession());
	}

	// Find entities with criteria
	public List<T> findByCriteria(DetachedCriteria criteria) {
		return getHibernateTemplate().findByCriteria(criteria);
	}

	// Find entities with criteria, with scoped result
	public List<T> findByCriteria(DetachedCriteria criteria, int firstResult,
			int maxResults) {
		return getHibernateTemplate().findByCriteria(criteria, firstResult,
				maxResults);
	}

	//  Find entities with certain entity and properties (meet property=value,except PK)
	public List<T> findEqualByEntity(T entity, String[] propertyNames) {
		Criteria criteria = this.createCriteria();
		Example exam = Example.create(entity);
		exam.excludeZeroes();
		String[] defPropertys = getSessionFactory().getClassMetadata(
				entityClass).getPropertyNames();
		for (String defProperty : defPropertys) {
			int ii = 0;
			for (ii = 0; ii < propertyNames.length; ++ii) {
				if (defProperty.equals(propertyNames[ii])) {
					criteria.addOrder(Order.asc(defProperty));
					break;
				}
			}
			if (ii == propertyNames.length) {
				exam.excludeProperty(defProperty);
			}
		}
		criteria.add(exam);
		return (List<T>) criteria.list();
	}

	// ʹ��ָ����ʵ�弰���Լ������������� like ��ʵ��ֵ�����Find entities with certain entity and properties (meet property like)
	public List<T> findLikeByEntity(T entity, String[] propertyNames) {
		Criteria criteria = this.createCriteria();
		for (String property : propertyNames) {
			try {
				Object value = PropertyUtils.getProperty(entity, property);
				if (value instanceof String) {
					criteria.add(Restrictions.like(property, (String) value,
							MatchMode.ANYWHERE));
					criteria.addOrder(Order.asc(property));
				} else {
					criteria.add(Restrictions.eq(property, value));
					criteria.addOrder(Order.asc(property));
				}
			} catch (Exception ex) {
				// Ignore result no effect
			}
		}
		return (List<T>) criteria.list();
	}

	//  Get row number with criteria
	public Integer getRowCount(DetachedCriteria criteria) {
		criteria.setProjection(Projections.rowCount());
		List list = this.findByCriteria(criteria, 0, 1);
		return (Integer) list.get(0);
	}

	// Find entities with criteria, return (max,min,avg,sum)
	public Object getStatValue(DetachedCriteria criteria, String propertyName,
			String StatName) {
		if (StatName.toLowerCase().equals("max"))
			criteria.setProjection(Projections.max(propertyName));
		else if (StatName.toLowerCase().equals("min"))
			criteria.setProjection(Projections.min(propertyName));
		else if (StatName.toLowerCase().equals("avg"))
			criteria.setProjection(Projections.avg(propertyName));
		else if (StatName.toLowerCase().equals("sum"))
			criteria.setProjection(Projections.sum(propertyName));
		else
			return null;
		List list = this.findByCriteria(criteria, 0, 1);
		return list.get(0);
	}
	
	// -------------------------------- SQL --------------------------------
	public void executeSQL(final String sql, final Map<String, Object> params){
		getHibernateTemplate().execute(new HibernateCallback<Object>() {
			public Object doInHibernate(Session s)
					throws HibernateException, SQLException {
				SQLQuery query = s.createSQLQuery(sql);
				if(params != null){
					for(String key : params.keySet())
						query.setParameter(key, params.get(key));
				}
				query.executeUpdate();
				return null;
			}
		});
	}

	// -------------------------------- Others --------------------------------

	// Lock entity
	public void lock(T entity, LockMode lock) {
		getHibernateTemplate().lock(entity, lock);
	}

	// Initialize entity
	public void initialize(Object proxy) {
		getHibernateTemplate().initialize(proxy);
	}

	// Flush
	public void flush() {
		getHibernateTemplate().flush();
	}
}